/*************************************************************************  
 *   This file is part of UBBMPSREADER.
 *
 *   UBBMPSREADER is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   UBBMPSREADER is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with UBBMPSREADER.  If not, see <http://www.gnu.org/licenses/>. 
 *************************************************************************/

package cs.ubbcluj.ro.mps.data;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

import cs.ubbcluj.ro.common.CommonTools;
import cs.ubbcluj.ro.datatypes.interfaces.*;
import cs.ubbcluj.ro.datatypes.implementations.*;
//import cs.ubbcluj.ro.mps.data.enumerations.*;
import cs.ubbcluj.ro.mps.bridge.implementation.NativeMpsImp;
import cs.ubbcluj.ro.mps.bridge.implementation.SparseMpsImp;
import cs.ubbcluj.ro.mps.bridge.interfaces.MpsImpInterface;
import cs.ubbcluj.ro.mps.data.interfaces.*;

/**
 * Represents an MPS file with the parsed data
 * 
 * @author avadas
 * @version 1.0
 */
public strictfp class MpsImpl implements Mps {
	
	MpsImpInterface imp;

    /** The logger */
    Logger LOG = CommonTools.getLogger(MpsImpl.class);

    /** The name of the MPS file */
    private String name;

    /** The constrains of the MPS file - elements of the ROW section */
    private final Map<String, Constrain> constrains = new LinkedHashMap<String, Constrain>();

    /** The RHS section (constrain->(variableName-> coefficient)) */
    private final Map<String, Map<String, Double>> rhs = new HashMap<String, Map<String, Double>>();

    /** The RHS section (rangeName->(constrain-> value)) */
    private final Map<String, Map<String, Double>> ranges = new HashMap<String, Map<String, Double>>();

    /** The variable index - all variables in the order of read */
    private final Set<String> variableIndex = new LinkedHashSet<String>();

    /** Target functions */
    private final Map<String, Constrain> targetFunction = new LinkedHashMap<String, Constrain>();

    /** Stores the upper bounds of a variables */
    private final Map<String, Double> upperBounds = new HashMap<String, Double>();

    /** Represents the lower bounds of variables */
    private final Map<String, Double> lowerBounds = new HashMap<String, Double>();

    
    public MpsImpl(MpsImpInterface mpsImp){
    	this.imp=mpsImp;
    }
    
    /**
     * Sets the name of the MPS file
     * 
     * @param name
     *            the name of the MPS file
     */
    @Override
    public void setName(final String name) {
	this.name = name;
	this.LOG.info("MPS name: " + name);
    }

   
    /**
     * Returns the name of the MPS file
     * 
     * @return the name of the MPS file
     */
    @Override
    public String getName() {
	return this.name;
    }

    /**
     * Adds a constrain (an element from the ROW section)
     * 
     * @param constrain
     *            the constrain to add
     */
    @Override
    public void addConstrain(final ConstrainImpl constrain) {
	if (constrain.getType() != ConstrainType.N) {
	    this.constrains.put(constrain.getName(), constrain);
	    this.LOG.info("Added constrain (from ROW section): "
		    + constrain.getName());
	} else {
	    this.targetFunction.put(constrain.getName(), constrain);
	    this.LOG.info("Added target function (from ROW section): "
		    + constrain.getName());
	}

    }

    /**
     * Adds a variable and its' coefficient to a given constrain
     * 
     * @param name
     *            the name of the constrain
     * @param variableName
     *            the variableName
     * @param coeficient
     *            the coefficient
     * @throws Exception
     */
    @Override
    public void addVariableToConstrain(final String name,
	    final String variableName, final double coeficient)
	    throws Exception {
	Constrain constrain = this.constrains.get(name);
	if (constrain == null) {
	    constrain = this.targetFunction.get(name);
	}
	if (constrain == null) {
	    this.LOG.error("Undefined constrain!");
	    throw new Exception("Undefined constrain!");
	} else {
	    this.variableIndex.add(variableName);
	    constrain.addVariable(variableName, coeficient);
	    this.LOG.info("Added variable to constrain " + name + " Variable: "
		    + variableName + " Value: " + coeficient);
	}
    }

    /**
     * Returns the elements of the ROW section
     * 
     * @return all constrains of this MPS file
     */
    @Override
    public Map<String, Constrain> getConstrains() {
	return this.constrains;
    }

    /**
     * Adds a constrian to RHS (represents b)
     * 
     * @param name
     *            the name of the constrain
     * @param rhsName
     *            the rhs name (in most cases b)
     * @param value
     *            the value of the constrain
     */
    @Override
    public void addConstrainToRhs(final String name, final String rhsName,
	    final double value) {
	if (this.rhs.containsKey(rhsName)) {
	    this.rhs.get(rhsName).put(name, value);
	} else {
	    final HashMap<String, Double> constrains = new LinkedHashMap<String, Double>();
	    for (final String otherConstrainName : this.constrains.keySet()) {
		constrains.put(otherConstrainName, 0d);
	    }
	    constrains.put(name, value);
	    this.rhs.put(rhsName, constrains);
	}
	this.LOG.info("Added value to RHS: " + rhsName + " Constrain: " + name
		+ " Value: " + value);
    }

    public int countLGConstrains() {
	int count = 0;
	for (final Constrain constrain : this.constrains.values()) {
	    if (constrain.getType() == ConstrainType.L
		    || constrain.getType() == ConstrainType.G) {
		count++;
	    }
	}
	return count;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addVariableBound(final String boundName,
	    final String variableName, final BoundsType boundType,
	    final Double boundValue) {
	switch (boundType) {
	case LO: {
	    this.lowerBounds.put(variableName, boundValue);
	    this.LOG.info("Added LOVER BOUND " + boundName + " to variable "
		    + variableName + " value " + boundValue);
	    break;
	}
	case UP: {
		
	    this.upperBounds.put(variableName, boundValue);
	    this.LOG.info("Added UPPER BOUND " + boundName + " to variable "
		    + variableName + " value " + boundValue);
	    break;
	}
	case FX: {
	    this.lowerBounds.put(variableName, boundValue);
	    this.upperBounds.put(variableName, boundValue);
	    this.LOG.info("Added FIXED[LOVER, UPPER] BOUND " + boundName
		    + " to variable " + variableName + " value " + boundValue);
	    break;
	}
	case FR: {
	    this.lowerBounds.put(variableName, Double.MIN_VALUE);
	    this.upperBounds.put(variableName, Double.MAX_VALUE);
	    this.LOG.info("Added FREE BOUND " + boundName + " to variable "
		    + variableName + " value " + boundValue);
	    break;
	}
	case PL: {
	    this.lowerBounds.put(variableName, 0d);
	    this.upperBounds.put(variableName, Double.MAX_VALUE);
	    this.LOG.info("Added PL BOUND " + boundName + " to variable "
		    + variableName + " value " + boundValue);
	    break;
	}
	case MI: {
	    this.lowerBounds.put(variableName, Double.MIN_VALUE);
	    this.upperBounds.put(variableName, Double.MAX_VALUE);
	    this.LOG.info("Added MI BOUND " + boundName + " to variable "
		    + variableName + " value " + boundValue);
	    break;
	}
	}

    }

     /**
     * {@inheritDoc}
     */
    @Override
    public int getNumberOfVariables() {
	return this.variableIndex.size();
    }
    
    public Set<String> getVariableIndex(){
    	return this.variableIndex;
    }
    
    public Map<String, Map<String, Double>> getRhs(){
    	return this.rhs;
    }
    
    public Map<String, Constrain> getTargetFunction(){
    	return this.targetFunction;
    }
    
    public Map<String, Double> getLowerBound(){
    	return this.lowerBounds;
    }
    
    public Map<String, Double> getUpperBound(){
    	return this.upperBounds;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public int getNumberOfConstrains() {
	return this.constrains.keySet().size();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addConstrainToRanges(final String name, final String rangeName,
	    final double value) {
	if (this.ranges.containsKey(rangeName)) {
	    this.ranges.get(rangeName).put(name, value);
	} else {
	    final HashMap<String, Double> constrains = new LinkedHashMap<String, Double>();
	    for (final String otherConstrainName : this.constrains.keySet()) {
		constrains.put(otherConstrainName, 0d);
	    }
	    constrains.put(name, value);
	    this.ranges.put(rangeName, constrains);
	}
	this.LOG.info("Added value to RANGE: " + rangeName + " Constrain: "
		+ name + " Value: " + value);
    }
    
    public Matrix getCoefficients() {
		return imp.getCoefficients();
	}
	
	public Vector getRightHandSideValues(){
		return imp.getRightHandSideValues();
	}
	
	public Vector getTargetCoefficients(){
		return imp.getTargetCoefficients();
	}
	
	public Vector getLowerBounds(){
		return imp.getLowerBounds();
	}
	
	public Vector getUpperBounds(){
		return imp.getUpperBounds();
	}

}
